package com.nado.extensions.util;

import java.util.*;

@SuppressWarnings("unchecked")
public class GenericMap<T> implements Map<String, Object>, Iterable<Map.Entry<String, T>> {

	protected Map<String, Object> map;	// value : T or ManagedList<T>

	public GenericMap() {
		this.map = new LinkedHashMap<String, Object>();
	}

	public Object get(Object key) {
		return map.get(key);
	}

	public Object remove(Object key) {
		return map.remove(key);
	}

	public Object put(String key, Object value) {
		return map.put(key, value);
	}

	public void putAll(Map m) {
		map.putAll(m);
	}

	public int size() {
		return map.size();
	}

	public boolean isEmpty() {
		return map.isEmpty();
	}

	public void clear() {
		map.clear();
	}

	public Set<String> keySet() {
		return map.keySet();
	}

	public Collection<Object> values() {
		return map.values();
	}

	public Set<Map.Entry<String, Object>> entrySet() {
		return map.entrySet();
	}

	public Iterator<Map.Entry<String, T>> iterator() {
		final Iterator<Map.Entry<String, Object>> entryIterator = entrySet().iterator();
		return new Iterator<Map.Entry<String, T>>() {

			private int index;
			private String field;
			private List<T> current;

			public boolean hasNext() {
				return (current != null && current.size() > index) || entryIterator.hasNext();
			}

			public Map.Entry<String, T> next() {
				if (current != null && current.size() > index) {
                    return new SimpleEntry(field, current.get(index++));
				}
				current = null;
				Map.Entry<String, Object> entry = entryIterator.next();
				String field = entry.getKey();
				if (entry.getValue() instanceof ManagedList) {
					this.index = 0;
					this.field = field;
					this.current = (List<T>) entry.getValue();
                    return new SimpleEntry(field, current.get(index++));
				}
                return new SimpleEntry(field, entry.getValue());
			}

			public void remove() {
				if (current != null && current.size() > index) {
					current.remove(index);
				} else {
					entryIterator.remove();
				}
			}
		};
	}

	public boolean containsKey(Object key) {
		return map.containsKey(key instanceof DomainModel.FIELD ? ((DomainModel.FIELD) key).name() : key);
	}

	public boolean containsValue(Object value) {
		for (Map.Entry<String, Object> entry : map.entrySet()) {
			Object valueList = entry.getValue();
			if (valueList instanceof ManagedList) {
				if (((ManagedList) valueList).contains(value)) {
					return true;
				}
			} else if (value.equals(valueList)) {
				return true;
			}
		}
		return false;
	}

	public int hashCode() {
		return map.hashCode();
	}

	public boolean equals(Object obj) {
		if (!(obj instanceof GenericMap)) {
			return false;
		}
		GenericMap another = (GenericMap) obj;
		return map.equals(another.map);
	}

	public String toString() {
		return map.toString();
	}

	protected void setElement(String key, T value) {
		map.put(key, value);
	}

	protected void addElement(String key, T value) {
		if (!map.containsKey(key)) {
			map.put(key, value);
			return;
		}
		Object prev = map.get(key);
		if (prev instanceof ManagedList) {
			((List) prev).add(value);
		} else {
			List newList = new ManagedList();
			newList.add(prev);
			newList.add(value);
			map.put(key, newList);
		}
	}

	protected void setList(String key, List value) {
		map.put(key, new ManagedList<T>(value));
	}

	protected void addList(String key, List toadd) {
		if (!map.containsKey(key)) {
			map.put(key, new ManagedList<T>(toadd));
			return;
		}
		Object prev = map.get(key);
		if (prev instanceof ManagedList) {
			((List) prev).addAll(toadd);
		} else {
			List newList = new ManagedList();
			newList.add(prev);
			newList.addAll(toadd);
			map.put(key, newList);
		}
	}

	protected T getElement(String key) {
		return getElement(key, 0, null);
	}

	protected T getElement(String key, int nth) {
		return getElement(key, nth, null);
	}

	protected T getElement(String key, int nth, T defaultValue) {
		boolean exsist = defaultValue == null || map.containsKey(key);	 // mask null value
		Object value = map.get(key);
		if (value instanceof ManagedList) {
			List values = (List) value;
			return values.size() > nth ? (T) values.get(nth) : defaultValue;
		}
		return exsist && nth == 0 ? (T) value : defaultValue;
	}

	protected T removeElement(DomainModel.FIELD key, int nth, T defaultValue) {
		if (!map.containsKey(key.name())) {
			return defaultValue;
		}
		Object value = nth == 0 ? map.remove(key.name()) : map.get(key.name());
		if (value instanceof ManagedList) {
			List values = (List) value;
			T result = values.size() > nth ? (T) values.remove(nth) : defaultValue;
			if (nth > 0 && values.isEmpty()) {
				map.remove(key.name());
			} else if (nth == 0 && !values.isEmpty()) {
				map.put(key.name(), values);
			}
			return result;
		}
		return nth == 0 ? (T) value : defaultValue;
	}

	protected List<T> getList(String key) {
		if (!map.containsKey(key)) {
			return Collections.emptyList();
		}
		Object prev = map.get(key);
		if (prev instanceof ManagedList) {
			return new ArrayList<T>((List) prev);
		}
		return new ArrayList<T>(Arrays.asList((T) prev));
	}

	protected List<T> removeList(String key) {
		if (!map.containsKey(key)) {
			return Collections.emptyList();
		}
		Object prev = map.remove(key);
		if (prev instanceof ManagedList) {
			return new ArrayList<T>((List) prev);
		}
		return new ArrayList<T>(Arrays.asList((T) prev));
	}

	// marker class
	protected class ManagedList<E> extends ArrayList<E> {
		public ManagedList() { super(); }
		public ManagedList(int initial) { super(initial); }
		public ManagedList(Collection<? extends E> c) { super(c); }
	}
}
